// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
 * Description: Entry point of liblinux backend
 * Author: Huawei OS Kernel Lab
 * Create: Wed Jul 10 16:58:35 2019
 */

#include "libdh_linux.h"

#include <hongmeng/errno.h>
#include <libstrict/strict.h>
#include <libpreempt/preempt.h>
#include <libhwsecurec/securec.h>

#include <devhost/log.h>
#include <devhost/security.h>
#include <libhmsrv_sys/hm_mem.h>
#include <libmem/utils.h>
#include <lnxbase/lnxbase.h>
#include <lnxbase/helper.h>

#include "cpuhp.h"

static unsigned int liblinux_boot_cpu_id;

static struct lnxbase_ctx *g_ctx;

/* liblinux post-init hook */
static int liblinux_postinit(void *arg)
{
	UNUSED(arg);
	unsigned long boot_affinity;

	if (lnxbase_load_extra_modules(g_ctx))
		dh_panic("load ext mod failed\n");

	/* Ensure that no sched migration occurs during kernel init */
	save_boot_affinity(&boot_affinity, liblinux_boot_cpu_id);

	/* do initcalls */
	liblinux_do_initcalls();

	restore_boot_affinity(boot_affinity);

	return E_HM_OK;
}

int dh_post_dev_populate_call(void)
{
	return lnxbase_root_notify(g_ctx);
}

/* 2048 is enough for both aarch64 and arm32
 * on arm32, the command line will be truncated to 1024 by linux automatically */
#define COMMAND_LINE_SIZE 2048
char boot_command_line[COMMAND_LINE_SIZE];

#ifdef __clang__
#define __no_stack_protector	__attribute__((no_stack_protector))
#else
#define __no_stack_protector	__attribute__((optimize("-fno-stack-protector")))
#endif

extern void *__fdt_pointer;
int __no_stack_protector dh_backend_init(struct dh_backend_ctor *ctor, const char *backend_cmdline)
{
	int ret;
	unsigned long boot_affinity;

	dh_info("initializing linux subsystem ...\n");

	liblinux_drivers_autoprobe = 1; /* enable driver autoprobe */

	__fdt_pointer = __lnxbase_acquire_bootfdt(NULL, LNXBASE_FWTYPE_FDT);
	if (__fdt_pointer == NULL) {
		dh_info("bootfdt is not supported\n");
		liblinux_devices_autoprobe = 0; /* disable device autoprobe */
	} else {
		liblinux_devices_autoprobe = 1; /* enable device autoprobe */
	}

	if (backend_cmdline != NULL) {
		ret = strcpy_s(boot_command_line, COMMAND_LINE_SIZE, backend_cmdline);
		if (ret != 0) {
			dh_error("boot command line is too long: %s\n", backend_cmdline);
			return E_HM_INVAL;
		}
	}

	/* Ensure that no sched migration occurs during kernel init */
	liblinux_boot_cpu_id = liblinux_pal_processor_id();
	save_boot_affinity(&boot_affinity, liblinux_boot_cpu_id);

	liblinux_kernel_init();

	restore_boot_affinity(boot_affinity);

	lnxbase_register_reclaim_func(&liblinux_drop_slab, "liblinux_drop_slab");

	ret = lnxbase_helper_simple_init(ctor, liblinux_postinit,
					 (void *)devhost_get_backend_args(),
					 &g_ctx);
	if (ret < 0)
		return ret;

	if (liblinux_devices_autoprobe)
		lnxbase_enable_device_autoprobe(g_ctx);

	if (__fdt_pointer != NULL)
		lnxbase_enable_bootfdt(g_ctx);

	/*
	 * override default alloc_page in lnxbase helper,
	 * use `GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO` as gfp flags
	 */
	ctor->alloc_page = dh_backend_alloc_page;
	ctor->free_page  = dh_backend_free_page;
	ret = lnxbase_enable_post_dev_populate(g_ctx, dh_extralib_init);
	if (ret < 0) {
		lnxbase_destroy(g_ctx);
		g_ctx = NULL;
		return ret;
	}

	return 0;
}

/*
 * This depends on the raw thread layout in ulibs:
 *    struct raw thread
 *        ...
 *        struct pthread
 *            ...
 *            tsd[key]  : struct task_struct
 *              |
 *            offset
 *              |
 *    TLS reg pointer
*/
#define TLS_TO_CURRENT(tls, offset, key) \
	((*(void ***)((tls) - (offset)))[(key)])

static unsigned int thread_offset;
static unsigned int thread_key;
#ifdef __aarch64__
void *liblinux_thread_get_my_data(void)
{
	unsigned long tls = 0UL;

	asm volatile ("mrs %0, tpidr_el0\n" : "=r"(tls));
	return TLS_TO_CURRENT(tls, thread_offset, thread_key);
}
#else
# error "support ARM64 only"
#endif

void liblinux_raw_thread_init(void)
{
	liblinux_pal_thread_specific_init(&thread_offset, &thread_key);
}

int liblinux_kasan_populate_shadow(const void *addr, size_t len)
{
	return hm_mem_mlock(PAGE_ALIGN_DOWN(addr), PAGE_ALIGN_UP(len));
}
